package com.youlu.campus.service.course;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConfig;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil;
import com.youlu.campus.base.exception.BusinessException;
import com.youlu.campus.entity.WechatConfigInfo;
import com.youlu.campus.entity.WechatMerchantConfig;
import com.youlu.campus.entity.course.UserCourseOrder;
import com.youlu.campus.entity.course.dto.GoodsOrderDTO;
import com.youlu.campus.service.utils.CommonUtils;
import com.youlu.campus.service.utils.PayUtil;
import com.youlu.campus.service.wechat.WechatConfigInfoService;
import com.youlu.campus.service.wechat.WechatMerchantConfigService;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;

/**
 * 微信支付v2文档 https://pay.weixin.qq.com/wiki/doc/api/jsapi_sl.php?chapter=9_4
 *
 * @author dengbo
 * @date 2019/11/05 下午4:47
 */
@Slf4j
@Service
public class WechatPayService {
    private static final String TRANSFER_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private WechatConfigInfoService wechatConfigInfoService;
    @Autowired
    private WechatMerchantConfigService wechatMerchantConfigService;

    @Data
    public static class WechatPayDTO {
        private String mchNo;
        private String appId;
        private String appSecret;
        private String payRequestUrl;
        private String refundRequestUrl;
    }

    public static String getSign(Map<String, Object> map, String key) {
        ArrayList<String> list = new ArrayList<String>();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (null != entry.getValue() && !"".equals(entry.getValue())) {
                list.add(entry.getKey() + "=" + entry.getValue() + "&");
            }
        }
        int size = list.size();
        String[] arrayToSort = list.toArray(new String[size]);
        Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < size; i++) {
            sb.append(arrayToSort[i]);
        }
        String result = sb.toString();
        result += "key=" + key;
        result = md5(result, "UTF-8").toUpperCase();
        return result;
    }

    public static String md5(String value, String charset) {
        MessageDigest md = null;
        try {
            byte[] data = value.getBytes(charset);
            md = MessageDigest.getInstance("MD5");
            byte[] digestData = md.digest(data);
            return toHex(digestData);
        } catch (NoSuchAlgorithmException e) {
            log.error("WechatPayService md5 algorithm error", e);
        } catch (UnsupportedEncodingException e) {
            log.error("WechatPayService md5 encoding error", e);
        }
        return null;
    }

    public static String toHex(byte input[]) {
        if (input == null) {
            return null;
        }
        StringBuffer output = new StringBuffer(input.length * 2);
        for (int i = 0; i < input.length; i++) {
            int current = input[i] & 0xff;
            if (current < 16) {
                output.append("0");
            }
            output.append(Integer.toString(current, 16));
        }

        return output.toString();
    }

    public WXPayConfig getWxPayConfig(String appId, WechatMerchantConfig merchant, boolean useCert) {
        return new WXPayConfig() {
            @Override
            public String getAppID() {
                return appId;
            }

            @Override
            public String getMchID() {
                return merchant.getMerchantId();
            }

            @Override
            public String getKey() {
                return merchant.getDesc();
            }

            @Override
            public InputStream getCertStream() {
                if (!useCert) {
                    return null;
                }
                if (StringUtils.isBlank(merchant.getCc())) {
                    log.error("WechatPayService,getWxPayConfig get cert is empty");
                    throw null;
                }
                File f = new File(merchant.getCc());
                if (!f.exists()) {
                    log.error("WechatPayService,getWxPayConfig cert file not exist");
                    return null;
                }
                try {
                    FileInputStream fs = new FileInputStream(f);
                    return fs;
                } catch (FileNotFoundException e) {
                    log.error("WechatPayService,getWxPayConfig get cert error", e);
                }
                return null;
            }

            @Override
            public int getHttpConnectTimeoutMs() {
                return 10000;
            }

            @Override
            public int getHttpReadTimeoutMs() {
                return 10000;
            }
        };
    }

    public enum WechatType {
        PAY,
        REFUND,
        TRANSFER,
        PROFILSHARING,
        ;
    }

    private Integer random(int max) {
        if (max == 0) {
            return 0;
        }
        return ThreadLocalRandom.current().nextInt(max);
    }

    public WechatMerchantConfig getRandomMerchant(String appId) {
        WechatConfigInfo wechatConfigInfo = wechatConfigInfoService.find(appId);
        if (Objects.isNull(wechatConfigInfo) || CollectionUtils.isEmpty(wechatConfigInfo.getMerchantIds())) {
            log.error("UserCourseOrderService,createOrder,appId={} not has merchant info", appId);
            throw new BusinessException("当前支付人数多,稍后重试");
        }
        String merchantId = wechatConfigInfo.getMerchantIds().get(random(wechatConfigInfo.getMerchantIds().size()));
        WechatMerchantConfig merchant = getWechatMerchantConfigById(merchantId);
        if (Objects.isNull(merchant)) {
            log.error("UserCourseOrderService,createOrder,merchant info not exist", merchantId);
            throw new BusinessException("微信支付异常,请联系在线客服");
        }
        return merchant;
    }

    public WechatMerchantConfig getWechatMerchantConfigById(String merchantId) {
        WechatMerchantConfig merchant = wechatMerchantConfigService.findByMerchantIdAndDecrypt(merchantId);
        if (Objects.isNull(merchant)) {
            log.error("UserCourseOrderService,createOrder,merchant info not exist", merchantId);
            throw new BusinessException("微信支付异常,请联系在线客服");
        }
        return merchant;
    }

    /**
     * 微信支付
     *
     * @param dto
     * @param merchant
     * @param notifyUrl
     * @return
     */
    public Triple<Boolean, Object, String> unifiedOrder(GoodsOrderDTO dto, WechatMerchantConfig merchant, String notifyUrl) {
        log.info("WechatMiniService,unifiedOrder,request param:{}", dto);
        String nonceStr = CommonUtils.getRandomStringByLength(32);
        Map<String, String> resultMap = null;
        WXPayConfig config = getWxPayConfig(dto.getAppId(), merchant, false);
        try {
            WXPay wxpay = new WXPay(config);
            Map<String, String> param = new HashMap<>(12);
            //公众账号appid
            param.put("appid", config.getAppID());
            //商户号
            param.put("mch_id", config.getMchID());
            //随机字符串
            param.put("nonce_str", nonceStr);
            String goodsName = dto.getGoodsName();
            if (StringUtils.isBlank(goodsName)) {
                goodsName = "课程";
            } else {
                int length = dto.getGoodsName().length() > 128 ? 128 : dto.getGoodsName().length();
                goodsName = dto.getGoodsName().substring(0, length);
            }
            //商品描述
            param.put("body", goodsName);
            //商户订单号
            param.put("out_trade_no", dto.getOrderNo());
            param.put("attach", "订单支付");
            String profitSharingStr = (dto.getProfitSharing() == null || !dto.getProfitSharing()) ? "N" : "Y";
            param.put("profit_sharing", profitSharingStr);
            //订单有效时间
            if (dto.getStartTime() != null) {
                String startTime = dto.getStartTime().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
                param.put("time_start", startTime);
            }
            if (dto.getExpireTime() != null) {
                String expire = dto.getExpireTime().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
                param.put("time_expire", expire);
            }
            //支付类型
            param.put("sign_type", "MD5");
            //openid
            param.put("openid", dto.getOpenId());
            param.put("total_fee", dto.getPrice().toString());
            //客户端Ip地址
            param.put("spbill_create_ip", dto.getClientIp());
            //通知地址
            param.put("notify_url", notifyUrl);
            //交易类型
            param.put("trade_type", "JSAPI");
            // 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
            String prestr = PayUtil.createLinkString(param);
            //MD5运算生成签名，这里是第一次签名，用于调用统一下单接口
            String mysign = PayUtil.sign(prestr, config.getKey(), "utf-8").toUpperCase();
            param.put("sign", mysign);
            resultMap = wxpay.unifiedOrder(param, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs());
        } catch (Exception e) {
            log.error("unifiedOrder,openId:{} orderNo:{},pay,error", dto.getOpenId(), dto.getOrderNo(), e);
        }
        Triple<Boolean, Map, String> re = getWechatResult(resultMap, dto.getOrderNo(), WechatType.PAY);
        if (!re.getLeft()) {
            return Triple.of(false, null, re.getRight());
        }
        //支付成功
        String prepayId = resultMap.get("prepay_id");
        Map map = new HashMap();
        map.put("appId", config.getAppID());
        map.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
        map.put("package", "prepay_id=" + prepayId);
        map.put("nonceStr", PayUtil.generateNonceStr());
        map.put("signType", "MD5");
        try {
            String sign = WXPayUtil.generateSignature(map, config.getKey());
            map.put("paySign", sign);
            map.put("orderNo", dto.getOrderNo());
            return Triple.of(true, map, null);
        } catch (Exception e) {
            log.error("unifiedOrder,openId:{} orderNo:{} sign error", dto.getOpenId(), dto.getOrderNo(), e);
            return Triple.of(false, null, "签名错误");
        }
    }

    /**
     * 微信转账
     *
     * @param dto
     * @param merchant
     * @return
     */
    public Triple<Boolean, Map, String> transfer(GoodsOrderDTO dto, WechatMerchantConfig merchant) {
        log.info("WechatMiniService,transfer,request param:{}", dto);
        String nonce_str = CommonUtils.getRandomStringByLength(32);
        WXPayConfig config = getWxPayConfig(dto.getAppId(), merchant, true);
        Map<String, String> resultMap = null;
        try {
            WXPay wxpay = new WXPay(config);
            Map<String, String> param = new HashMap<>(12);
            //公众账号appid
            param.put("mch_appid", config.getAppID());
            //商户号
            param.put("mchid", config.getMchID());
            //随机字符串
            param.put("nonce_str", nonce_str);
            //商户订单号
            param.put("partner_trade_no", dto.getOrderNo());
            //用户openid
            param.put("openid", dto.getOpenId());
            //校验用户姓名选项 OPTION_CHECK
            param.put("check_name", "NO_CHECK");
            //check_name设置为FORCE_CHECK或OPTION_CHECK，则必填
            if (StringUtils.isNotBlank(dto.getUserName())) {
                param.put("re_user_name", dto.getUserName());
            }
            //转账金额
            param.put("amount", dto.getPrice().toString());
            //企业付款描述信息
            param.put("desc", "回收打款");
            //服务器Ip地址
            if (StringUtils.isNotBlank(dto.getClientIp())) {
                param.put("spbill_create_ip", dto.getClientIp());
            }
            param = PayUtil.paraFilter(param);
            //MD5运算生成签名，这里是第一次签名，用于调用统一下单接口
            String mysign = WXPayUtil.generateSignature(param, config.getKey());
            param.put("sign", mysign);
            String uniformUrl = TRANSFER_URL;
            String resultXml = wxpay.requestWithCert(uniformUrl, param, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs());
            resultMap = WXPayUtil.xmlToMap(resultXml);
        } catch (Exception e) {
            log.error("transfer,openId:{} orderNo:{} transfer error", dto.getOpenId(), dto.getOrderNo(), e);
        }
        return getWechatResult(resultMap, dto.getOrderNo(), WechatType.TRANSFER);
    }

    /**
     * 微信订单查询
     *
     * @param dto
     * @param merchant
     * @return
     */
    public Triple<Boolean, Map, String> queryOrder(GoodsOrderDTO dto, WechatMerchantConfig merchant) {
        log.info("queryOrder,request param:{}", dto);
        String nonce_str = CommonUtils.getRandomStringByLength(32);
        WXPayConfig config = getWxPayConfig(dto.getAppId(), merchant, true);
        Map<String, String> resultMap = null;
        try {
            WXPay wxpay = new WXPay(config);
            Map<String, String> param = new HashMap<>(12);
            //公众账号appid
            param.put("mch_appid", config.getAppID());
            //商户号
            param.put("mchid", config.getMchID());
            //随机字符串
            param.put("nonce_str", nonce_str);
            //商户订单号
            param.put("out_trade_no", dto.getOrderNo());
            //MD5运算生成签名，这里是第一次签名，用于调用统一下单接口
            String mysign = WXPayUtil.generateSignature(param, config.getKey());
            param.put("sign", mysign);
            resultMap = wxpay.orderQuery(param);
            log.info("queryOrder, orderNo:{} pay result:{}",dto.getOrderNo(),resultMap);
        } catch (Exception e) {
            log.error("queryOrder error orderNo:{}", dto.getOrderNo(), e);
        }
        if (resultMap == null) {
            return Triple.of(false, null, "查询失败");
        }
        if (resultMap.get("return_code") == null || !resultMap.get("return_code").equals("SUCCESS")) {
            log.error("queryOrder orderNo:{} query fail msg:{}", dto.getOrderNo(), resultMap.get("return_msg"));
            return Triple.of(false, null, "查询通讯失败");
        }
        if (resultMap.get("result_code") == null || !resultMap.get("result_code").equals("SUCCESS")) {
            log.error("queryOrder orderNo:{} query fail error code:{},error msg:{}", dto.getOpenId(), dto.getOrderNo(), resultMap.get("err_code"), resultMap.get("err_code_des"));
            return Triple.of(false, null, "查询失败");
        }
        return Triple.of(true, resultMap, "");
    }



    /**
     * 微信退款
     *
     * @param dto
     * @param merchant
     * @return
     */
    public Triple<Boolean,Map,String> refund(GoodsOrderDTO dto, WechatMerchantConfig merchant) {
        log.info("WechatMiniService,refund,request param:{}", dto);
        Map<String, String> resultMap = null;
        String nonceStr = CommonUtils.getRandomStringByLength(32);
        WXPayConfig config = getWxPayConfig(dto.getAppId(), merchant, true);
        try {
            WXPay wxpay = new WXPay(config);
            Map<String, String> param = new HashMap<>(16);
            //退款单号
            param.put("appid", dto.getAppId());
            //商户号
            param.put("mch_id", config.getMchID());
            //总金额
            param.put("total_fee", dto.getPrice().toString());
            //退款金额
            param.put("refund_fee", dto.getRefundPrice().toString());
            //随机字符串
            param.put("nonce_str", nonceStr);
            //退款单号
            param.put("out_refund_no", dto.getRefundNo());
            //商户单号
            param.put("out_trade_no", dto.getOrderNo());
            //MD5运算生成签名，这里是第一次签名，用于调用统一下单接口
            String mysign = WXPayUtil.generateSignature(param, config.getKey());
            param.put("sign", mysign);
            resultMap = wxpay.refund(param);
        } catch (Exception e) {
            log.error("refund,openId:{} orderNo:{},refund error", dto.getOpenId(), dto.getOrderNo(), e);
        }
        return getWechatResult(resultMap, dto.getOrderNo(), WechatType.REFUND);
    }

    /**
     * https://pay.weixin.qq.com/wiki/doc/api/allocation.php?chapter=27_1&index=1
     * 分账
     */
    public Triple<Boolean,Map, String> profitsharing(GoodsOrderDTO dto, WechatMerchantConfig merchant) {
        log.info("WechatMiniService,refund,request param:{}", dto);
        Map<String, String> resultMap = null;
        String nonceStr = CommonUtils.getRandomStringByLength(32);
        WXPayConfig config = getWxPayConfig(dto.getAppId(), merchant, true);
        if (CollectionUtils.isEmpty(dto.getProfitsharingList())) {
            return Triple.of(false, null,"分账账户信息不存在");
        }
        try {
            WXPay wxpay = new WXPay(config);
            Map<String, String> param = new HashMap<>(16);
            //退款单号
            param.put("appid", dto.getAppId());
            //商户号
            param.put("mch_id", config.getMchID());
            //随机字符串
            param.put("nonce_str", nonceStr);
            //商户单号
            param.put("out_order_no", dto.getOrderNo());
            // 商家订单号
            param.put("transaction_id", dto.getTransactionId());
            //分账单号
            List<Object> list = dto.getProfitsharingList().stream().map(item -> {
                JSONObject json = new JSONObject();
                if ("2".equals(item.getType())) {
                    json.put("type", "PERSONAL_OPENID");
                } else {
                    json.put("type", "MERCHANT_ID");
                }
                json.put("account", item.getAccount());
                json.put("amount", item.getAmount());
                json.put("description", StringUtils.isBlank(item.getDescription())? "分账" : item.getDescription());
                return json;
            }).collect(Collectors.toList());
            param.put("receivers", list.toString());
            //HMACSHA256运算生成签名，这里是第一次签名，用于调用统一下单接口
            String mysign = WXPayUtil.generateSignature(param, config.getKey(), WXPayConstants.SignType.HMACSHA256);
            param.put("sign", mysign);
            log.info("param:{}",param);
            String uniformUrl = "https://api.mch.weixin.qq.com/secapi/pay/profitsharing";
            String resultXml = wxpay.requestWithCert(uniformUrl, param, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs());
            resultMap = WXPayUtil.xmlToMap(resultXml);
        } catch (Exception e) {
            log.error("orderNo:{},profitsharing error", dto.getOrderNo(), e);
        }
        Triple<Boolean, Map, String> re = getWechatResult(resultMap, dto.getOrderNo(), WechatType.PROFILSHARING);
        if (re.getLeft()) {
            return Triple.of(true, resultMap,null);
        }
        return Triple.of(false, null,re.getRight());
    }

    public Triple<Boolean, Map, String> getWechatResult(Map<String, String> resultMap, String orderNo, WechatType type) {
        log.info("getWechatResult,wechat type :{} orderNo:{} request result:{}", type, orderNo, JSON.toJSONString(resultMap));
        String errMsg = "通讯失败";
        switch (type) {
            case PAY:
                errMsg = "支付失败";
                break;
            case REFUND:
                errMsg = "退款失败";
                break;
            case TRANSFER:
                errMsg = "转账失败";
                break;
            case PROFILSHARING:
                errMsg = "分账失败";
                break;
            default:
                break;
        }
        if (resultMap == null) {
            return Triple.of(false, null, errMsg);
        }
        if (resultMap.get("return_code") == null || !resultMap.get("return_code").equals("SUCCESS")) {
            String error = resultMap.get("return_msg");
            log.error("getWechatResult orderNo:{} requst fail msg:{}", orderNo, error);
            return Triple.of(false, null, error == null ? errMsg : error);
        }
        if (resultMap.get("result_code") == null || !resultMap.get("result_code").equals("SUCCESS")) {
            String error = resultMap.get("err_code_des");
            log.error("getWechatResult orderNo:{} request fail error code:{},error msg:{}", orderNo, resultMap.get("err_code"), error);
            return Triple.of(false, null, error == null ? errMsg : error);
        }
        return Triple.of(true, resultMap, null);
    }

    public void thridPay(WechatPayDTO dto, UserCourseOrder order) {
        String url = dto.getPayRequestUrl();
        Map<String, Object> param = new HashMap<>();
        param.put("mchNo", dto.getMchNo());
        param.put("appId", dto.getAppId());
        param.put("amount", order.getPrice());
        param.put("mchOrderNo", order.getOrderNo());
        param.put("currency", "cny");
        int length = order.getName().length() > 128 ? 128 : order.getName().length();
        String goodsName = order.getName().substring(0, length);
        if (StringUtils.isNotBlank(goodsName)) {
            goodsName = "课程";
        }
        param.put("subject", goodsName);
        param.put("body", goodsName);
        param.put("wayCode", "WX_JSAPI");
        param.put("reqTime", System.currentTimeMillis());
        param.put("signType", "MD5");
        param.put("version", "1.0");
        Map<String, String> user = new HashMap<>();
        user.put("openid", order.getOrderNo());
        String userstr = JSON.toJSONString(user);
        param.put("channelExtra", userstr);
        String sign = getSign(param, dto.getAppSecret());
        param.put("sign", sign);
        Object re = restTemplate.postForEntity(url, param, Object.class);
        log.info("re:{}", re);
    }
}
